﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using NUnit.Framework;

namespace NotifiableLinqTests.FrameWork
{
	/// <summary>
	/// Слушает коллекцию и воспроизводит локальный кеш по информации из событий
	/// Попутно проверяет информацию в событии на достоверность
	/// </summary>
	class CollectionNotificationListener : IEnumerable
	{
		readonly ArrayList _list = new ArrayList();
		readonly IEnumerable _source;
		readonly INotifyCollectionChanged _sourceNotifiable;

		public CollectionNotificationListener(IEnumerable source)
		{
			_source = source;
			_sourceNotifiable = source as INotifyCollectionChanged;
			if (_source == null)
			{
				throw new ArgumentNullException("source");
			}
			if (_sourceNotifiable == null)
			{
				throw new ArgumentException("source is not INotifyCollectionChanged", "source");
			}
			Reload();
			_sourceNotifiable.CollectionChanged += Source_CollectionChanged;
		}

		#region IEnumerable Members

		public IEnumerator GetEnumerator()
		{
			return _list.GetEnumerator();
		}

		#endregion

		void Source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					Assert(e.NewItems != null, "Add: NewItems == null");
					Assert(e.NewItems.Count == 1);
					Assert(e.OldItems == null || e.OldItems.Count == 0);
					Assert(e.NewStartingIndex >= 0);
					Assert(e.NewStartingIndex <= _list.Count);
					Assert(Equals(SourceArray[e.NewStartingIndex], e.NewItems[0]));
					_list.Insert(e.NewStartingIndex, e.NewItems[0]);
					break;
				case NotifyCollectionChangedAction.Remove:
					Assert(e.OldItems != null);
					Assert(e.OldItems.Count == 1);
					Assert(e.NewItems == null || e.NewItems.Count == 0);
					Assert(e.OldStartingIndex >= 0);
					Assert(e.OldStartingIndex < _list.Count);
					Assert(_list.Count == SourceArray.Length + 1);
					_list.RemoveAt(e.OldStartingIndex);
					break;
				case NotifyCollectionChangedAction.Replace:
					throw new NotImplementedException("Replace");
					// break;
				case NotifyCollectionChangedAction.Move:
					throw new NotImplementedException("Move");
					// break;
				case NotifyCollectionChangedAction.Reset:
					Reload();
					break;
				default:
					throw new ArgumentOutOfRangeException("e", "Action is ArgumentOutOfRange");
			}

			CollectionAssert.AreEqual(SourceArray, _list.ToArray());
		}

		void Reload()
		{
			_list.Clear();
			foreach (var item in _source)
			{
				_list.Add(item);
			}
		}

		public override string ToString()
		{
			return "L" + _list.Count + ": " + string.Join(", ", _list.Cast<object>().Select(x => x.ToString()).ToArray());
		}

		#region Utils

		object[] SourceArray
		{
			get { return _source.OfType<object>().ToArray(); }
		}

		static void Assert(bool condition)
		{
			Assert(condition, null);
		}

		static void Assert(bool condition, string failureReason)
		{
			if (!condition)
			{
				throw new ApplicationException("Collection Notification Listener Failed: " + failureReason);
			}
		}

		#endregion
	}
}